import ConfigParser
import logging
import os
import urllib
from urllib2 import HTTPError
from friendmap.facebook.models import GeocodedLocation
from friendmap.lib.fusiontables.src.authorization.clientlogin import ClientLogin
from friendmap.lib.fusiontables.src import ftclient
from friendmap.lib.fusiontables.src.sql.sqlbuilder import SQL
from django.utils import simplejson
from google.appengine.runtime import DeadlineExceededError

FT_INSERTS_LIMIT = 500

def getJSONFormatFromFriends(friends):
    """ Produces a JSON text with all the information
        for displaying the friends
    Args:
        friends: a dictionary representing the friends data.
    Returns:
        Ex:
            [
                {
                    lat : "20.14472135665691",
                    lng : "-15.14472135665691",
                    people : [
                        {
                          "name": "Anna Mendel",
                          "id": "20034841"
                        },
                        {
                          "name": "Stevie Wonder",
                          "id": "106288792740854",
                        }
                    ]
                },
                {
                    lat : "41.14472135665691",
                    lng : "-40.14472135665691",
                    people : [
                        {
                          "name": "Mimi Rogers",
                          "id": "20034841"
                        }
                    ]
                },
            ]
    """
    localizedFriends = dict()
    noLocation = list()
    for friend in friends['data']:
        name = ''
        location = ''
        id = ''
        # TODO: code optimization: strange iteration
        for k,v in friend.iteritems():
            if k == 'name': name = v
            if k == 'location': location = v['name']
            if k == 'id': id = v
        if location is not None and len(location) > 1:
            geocoded_location = geocode(location)
            if geocoded_location not in localizedFriends:
                localizedFriends[geocoded_location] = []
            localizedFriends[geocoded_location].append({'name' : name, 'id' : id})
        else:
            logging.debug("Friend %s doesn't have a location specified",name)
            noLocation.append({'name':name,'id' : id})
    # format the output
    output = []
    for k in localizedFriends.iterkeys():
        lat,lng = k.split()
        output.append({'lat' : lat, 'lng' : lng, 'people' : localizedFriends[k]})

    return output,{'people' : noLocation}

def geocode(location):
    location = location.strip()
    db_location = GeocodedLocation.get_by_key_name(location)
    if db_location is None:
        gmaps_geocode = None
        #TODO: code clean: find an uniform way of treating these
        try:
            gmaps_geocode = urllib.urlopen(ConfMng().get_default_config().get('GOOGLE','maps_geocode_url') +
                                           urllib.urlencode({'address':location.encode('utf-8'), 'sensor':'false'}))
        except DeadlineExceededError:
            try:
                gmaps_geocode = urllib.urlopen(ConfMng().get_default_config().get('GOOGLE','maps_geocode_url') +
                                               urllib.urlencode({'address':location.encode('utf-8'), 'sensor':'false'}))
            except DeadlineExceededError:
                logging.warning("Could not geolocalize address %s. Response took too long.", location)
                return location
        gmaps_geocode_response = simplejson.loads(gmaps_geocode.read())
        if gmaps_geocode_response['status'] == 'OK':
            results = gmaps_geocode_response['results']
            glocation = None
            for result in results:
                if result['geometry'] is not None:
                    glocation = result['geometry']['location']
                    break
            if glocation is not None:
                db_location = GeocodedLocation(key_name=location,address=location,latitude=str(glocation['lat']),longitude=str(glocation['lng']))
                db_location.put()
            else:
                logging.warning("Could not find the results/geometry/location element in response: %s", results)
        else:
            logging.warning("Could not geolocalize address %s. Response: %s", location, gmaps_geocode_response['status'])
            return location
    else:
        logging.debug("Found localisation info for %s",location)
    return db_location.latitude + ' ' + db_location.longitude

class ConfMng():

    def get_config(self,fileName):
        # TODO: optimization : cache these type of objects in DB
        config = ConfigParser.SafeConfigParser()
        config.readfp(open(fileName))
        return config

    def get_default_config(self):
        return self.get_config(os.path.join(os.path.split(__file__)[0], 'site.cfg'))

class FTManagement():

    def __init__(self):
        self.config = ConfMng().get_default_config()

    def confget(self,section,field):
        return self.config.get(section,field)

    def login(self):
        username = self.confget('GOOGLE','username')
        password = self.confget('GOOGLE','password')

        token = ClientLogin().authorize(username, password)
        return token

    def build_inserts(self, friends, tableId, userId):
        inserts = list()
        noLocation = list()
        for friend in friends['data']:
            name = ''
            location = ''
            for k,v in friend.iteritems():
                if k == 'name': name = v
                if k == 'location': location = v['name']
            geocoded_location = ''
            if location is not None and len(location) > 1:
                geocoded_location = geocode(location)
            else:
                logging.debug("Friend %s doesn't have a location specified",name)
                noLocation.append(name)
            inserts.append(SQL().insert(tableId, {'facebook_id': userId,'name':name.replace("'","\\'"), 'location': geocoded_location}))

        return inserts,noLocation

    def delete_rows(self, tableId, userId, ft_client):
        deletes = list()
        deleteQuery = SQL().select(tableId,['ROWID'],'facebook_id=' + userId)
        rows = ft_client.query(deleteQuery)
        # first line contains the columns' names
        for rowId in rows.split()[1:]:
            if rowId is not None and len(rowId) > 1:
#                ft_client.query(SQL().delete(tableId, rowId))
#                logging.debug("Deleted friend for userId: %s", userId)
                deletes.append(SQL().delete(tableId, int(rowId)))
        if len(deletes) > 1:
            response = ft_client.query(" ".join(deletes))
            #self.execute_commands_list(deletes,ft_client)
            logging.debug("Deleted friends for userId: %s. Response %s", userId, response)
        else:
            logging.debug("No friends to delete for userId: %s", userId)

    def is_client_present(self, tableId, userId, ft_client):
        selectQuery = SQL().select(tableId,['ROWID'],'facebook_id=' + userId + " LIMIT 1")
        rows = ft_client.query(selectQuery)
        # first line contains the columns' names
        if len(rows.split()) > 1:
            logging.debug("Found friends for userId: %s. Response %s", userId, rows)
            return True
        else:
            logging.debug("Didn't find any friends for userId: %s. Response %s", userId, rows)
            return False

    def get_table_id(self, tableName, ft_client):
        tables = ft_client.query(SQL().showTables())
        for line in tables.splitlines():
            logging.debug("Analysing table:  %s", line)
            (ctableId,sep,ctableName) = line.partition(",")
            if ctableName == tableName:
                return int(ctableId)
        return -1

    def store_friends(self,userId,friends):
        """ Stores the friends' names and locations in a fusion table
        Use a facebook user id as a key.
        If the entries are already present just update

        Args:
            userId: the facebook id in graph
            friends: a dictionary representing the friends data.
            Ex:
                {
                  "data": [
                    {
                      "name": "Anna Mendel",
                      "gender": "female",
                      "id": "20034841"
                    },
                    {
                      "name": "Stevie Wonder",
                      "location": {
                        "id": "106288792740854",
                        "name": "Grenoble, France"
                      },
                      "gender": "male",
                      "id": "54635252"
                    },
                  ]
                }
        """
        try:
            token = self.login()
        except HTTPError:
            logging.error("Authentification with FT failed")
            return -1,[]

        ft_client = ftclient.ClientLoginFTClient(token)

        tableName = self.confget('GOOGLE','tablename')
        tableId = self.get_table_id(tableName,ft_client)
        if tableId == -1:
            logging.error("Couldn't find table:  %s", tableName)
            return -1,[]

        noLocation = None
        if not self.is_client_present(tableId, userId, ft_client):
            # insert data
            (inserts,noLocation) = self.build_inserts(friends,tableId,userId)
            self.execute_commands_list(inserts,ft_client,FT_INSERTS_LIMIT)
        else:
            noLocationFriends = ft_client.query(SQL().select(tableId,['name'],"facebook_id=%s AND location=''"%userId)).splitlines()
            if len(noLocationFriends) > 1: noLocation = noLocationFriends[1:]

        return tableId,noLocation

    def execute_commands_list(self, commands, ft_client, limitPerQuery):
        # a limit of 500 inserts is imposed by FT APIs
        logging.debug("Preparing to execute %d commands", len(commands))
        while True:
            chunk = commands
            if len(commands) > limitPerQuery:
                chunk = commands[0:limitPerQuery]
                commands = commands[limitPerQuery+1:]
            response = ft_client.query(";".join(chunk))
            logging.debug("Executed %d commands. Response: %s", len(chunk),response)
            # we finished
            if len(chunk) < limitPerQuery or len(commands) == 0: break
